/*
 * Copyright (C) 2016
 *
 * Brick Yang <printfxxx@163.com>
 *
 * This program is free software. You can redistribute it and/or
 * modify it as you like.
 */

/**
 * @file	bman.c
 * @brief	bman driver
 */

#include <linux/init.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/module.h>

#include <asm/io.h>

#include <mtrace.h>

#include "bman.h"

#define BM_VALID_BIT	((u32)0x80)

/* BCSP CINH register offsets */
#if defined(CONFIG_ARM64)

#define BCSP_CINH_RCR_PI	0x3000
#define BCSP_CINH_RCR_CI	0x3100
#define BCSP_CINH_CFG		0x3300
#define BCSP_CINH_SCN(n)	(0x3400 + ((u32)(n) << 6))
#define BCSP_CINH_ISR		0x3e00
#define BCSP_CINH_IER		0x3e40
#define BCSP_CINH_ISDR		0x3e80
#define BCSP_CINH_IIR		0x3ec0

#elif defined(CONFIG_PPC)

#define BCSP_CINH_RCR_PI	0x0000
#define BCSP_CINH_RCR_CI	0x0004
#define BCSP_CINH_CFG		0x0100
#define BCSP_CINH_SCN(n)	(0x0200 + ((u32)(n) << 2))
#define BCSP_CINH_ISR		0x0e00
#define BCSP_CINH_IER		0x0e04
#define BCSP_CINH_ISDR		0x0e08
#define BCSP_CINH_IIR		0x0e0c

#else
#error "Unsupported platform"
#endif

/* BCSP CENA register offsets */
#define BCSP_CENA_CR		0x0000
#define BCSP_CENA_RR(n)		(0x0100 + ((u32)(n) << 6))
#define BCSP_CENA_RCR(n)	(0x1000 + ((u32)(n) << 6))
#define BCSP_CENA_RCR_IDX(o)	(((u32)(o) - 0x1000) >> 6)
#define BCSP_CENA_RCR_PI	0x3000
#define BCSP_CENA_RCR_CI	0x3100

#if defined(CONFIG_ARM64)
#define dccvac(p)	{ asm volatile("dc cvac, %0;" : : "r" (p) : "memory"); }
#define dcivac(p)	{ asm volatile("dc ivac, %0;" : : "r" (p) : "memory"); }
#elif defined(CONFIG_PPC_E500MC)
#define dccvac(p)	{ asm volatile("dcbf 0,  %0;" : : "r" (p) : "memory"); }
#define dcivac(p)	dccvac(p)
#else
#error "Unsupported platform"
#endif

/* Portal Access */

static inline u32 bman_cinh_read(struct bman_swp *s, u32 offset)
{
	__be32 be32;

	be32 = __raw_readl(s->addr_cinh + offset);
	return be32_to_cpu(be32);
}

static inline void bman_cinh_write(struct bman_swp *s, u32 offset,
				   u32 val)
{
	__be32 be32;

	be32 = cpu_to_be32(val);
	__raw_writel(be32, s->addr_cinh + offset);
}

static inline void __iomem *bman_cinh_base(struct bman_swp *s, u32 offset)
{
	BUG_ON(!IS_ALIGNED(offset, SMP_CACHE_BYTES));
	return s->addr_cinh + offset;
}

static inline u32 bman_cena_read(struct bman_swp *s, u32 offset)
{
	__be32 be32;

	be32 = __raw_readl(s->addr_cena + offset);
	return be32_to_cpu(be32);
}

static inline void bman_cena_write(struct bman_swp *s, u32 offset,
				   u32 val)
{
	__be32 be32;

	be32 = cpu_to_be32(val);
	__raw_writel(be32, s->addr_cena + offset);
}

static inline void __iomem *bman_cena_base(struct bman_swp *s, u32 offset)
{
	BUG_ON(!IS_ALIGNED(offset, SMP_CACHE_BYTES));
	return s->addr_cena + offset;
}

static inline u8 bm_cyc_diff(u8 mask, u8 left, u8 right)
{
	return (right - left) & mask;
}

#define BCSP_CFG_WN_SHIFT	4
#define BCSP_CFG_RPM_SHIFT	0

static inline u32 bman_set_bcsp_cfg(u8 wn, u8 rpm)
{
	return wn << BCSP_CFG_WN_SHIFT |
	       rpm << BCSP_CFG_RPM_SHIFT;
}

struct bman_swp *bman_swp_init(const struct bman_swp_desc *d)
{
	u8 i, n;
	u32 reg;
	bool rcr_vb;
	void __iomem *p;
	struct bman_swp *s;

	if (!(s = kmalloc(sizeof(*s), GFP_KERNEL)) || MTRACE_KMEM_ADD(s)) {
		goto err;
	}
	s->desc = d;

	s->addr_cena = d->cena_bar;
	s->addr_cinh = d->cinh_bar;

	bman_cinh_write(s, BCSP_CINH_SCN(0), 0);
	bman_cinh_write(s, BCSP_CINH_SCN(1), 0);
	bman_cinh_write(s, BCSP_CINH_IER, 0);
	bman_cinh_write(s, BCSP_CINH_ISDR, 0xffffffff);
	bman_cinh_write(s, BCSP_CINH_IIR, 1);
	bman_cinh_write(s, BCSP_CINH_ISR, 0xffffffff);

	rcr_vb = (s->desc->flags & SWP_F_RCR_VB) ? true : false;
	reg = bman_set_bcsp_cfg(0,			/* Writes cacheable */
				rcr_vb ? 2 : 1);	/* RCR production notification mode */
	bman_cinh_write(s, BCSP_CINH_CFG, reg);

	p = bman_cena_base(s, BCSP_CENA_RR(1));
	dcivac(p);
	if (__raw_readb(p)) {
		s->cr.valid_bit = 0;
	} else {
		s->cr.valid_bit = BM_VALID_BIT;
	}

	s->rcr.pi_ci_mask = 0x7;
	reg = bman_cinh_read(s, BCSP_CINH_RCR_PI);
	s->rcr.valid_bit = reg & (s->rcr.pi_ci_mask + 1) ? BM_VALID_BIT : 0;
	s->rcr.pi = reg & s->rcr.pi_ci_mask;
	s->rcr.ci = bman_cinh_read(s, BCSP_CINH_RCR_CI) & s->rcr.pi_ci_mask;
	s->rcr.fi = s->rcr.pi;
	if (s->rcr.pi != s->rcr.ci) {
		pr_err("bman: RCR ring is not zero, portal is not clean\n");
		goto err;
	}

	n = d->cena_size >> L1_CACHE_SHIFT;
	for (i = 0; i < n; i++) {
		dcivac(d->cena_bar + (i << L1_CACHE_SHIFT));
	}

	return s;
err:
	if (s) {
		MTRACE_KMEM_DEL(s);
		kfree(s);
	}
	return NULL;
}

void bman_swp_finish(struct bman_swp *s)
{
	bman_cinh_write(s, BCSP_CINH_SCN(0), 0);
	bman_cinh_write(s, BCSP_CINH_SCN(1), 0);
	bman_cinh_write(s, BCSP_CINH_IER, 0);
	bman_cinh_write(s, BCSP_CINH_ISDR, 0xffffffff);
	bman_cinh_write(s, BCSP_CINH_IIR, 1);
	bman_cinh_write(s, BCSP_CINH_ISR, 0xffffffff);

	MTRACE_KMEM_DEL(s);
	kfree(s);
}

void __iomem *bman_swp_cr_next(struct bman_swp *s, u8 *cmd_verb)
{
	void __iomem *p;

	p = bman_cena_base(s, BCSP_CENA_CR);
	*cmd_verb = s->cr.valid_bit;

	return p;
}

const void __iomem *bman_swp_rr_next(struct bman_swp *s, u8 cmd_verb)
{
	const void __iomem *p;

	p = bman_cena_base(s, BCSP_CENA_RR(s->cr.valid_bit ? 1 : 0));
	if ((__raw_readb(p) & 0xf0) != (cmd_verb & 0xf0)) {
		dma_rmb();
		dcivac(p);
		prefetch(p);
		return NULL;
	}

	s->cr.valid_bit ^= BM_VALID_BIT;
	return p;
}

void __iomem *bman_swp_rcr_next(struct bman_swp *s, u8 *cmd_verb)
{
	u8 free;

	free = s->rcr.pi_ci_mask - bm_cyc_diff(s->rcr.pi_ci_mask, s->rcr.ci, s->rcr.pi);
	if (!free) {
		s->rcr.ci = bman_cinh_read(s, BCSP_CINH_RCR_CI) & s->rcr.pi_ci_mask;
		free = s->rcr.pi_ci_mask - bm_cyc_diff(s->rcr.pi_ci_mask, s->rcr.ci, s->rcr.pi);
	}

	if (free) {
		*cmd_verb = s->rcr.valid_bit;
		return bman_cena_base(s, BCSP_CENA_RCR(s->rcr.pi & s->rcr.pi_ci_mask));
	} else {
		return NULL;
	}
}

void bman_swp_cr_submit(struct bman_swp *s, void __iomem *p, u8 cmd_verb)
{
	dma_wmb();
	__raw_writeb(cmd_verb, p);
	dccvac(p);
	p = bman_cena_base(s, BCSP_CENA_RR(s->cr.valid_bit ? 1 : 0));
	dcivac(p);
	prefetch(p);
}

int bman_swp_rcr_submit(struct bman_swp *s, void __iomem *p, u8 cmd_verb, bool flush)
{
	u8 i, n;
	unsigned long flags;

	flags = s->desc->flags;

	if (p) {
		i = BCSP_CENA_RCR_IDX(p - bman_cena_base(s, 0));
		if (i != s->rcr.pi) {
			return -EINVAL;
		}
		if (flags & SWP_F_RCR_VB) {
			dma_wmb();
		}
		__raw_writeb(cmd_verb, p);
		dccvac(p);
		s->rcr.pi = (s->rcr.pi + 1) & s->rcr.pi_ci_mask;
		if ((flags & SWP_F_RCR_VB) && !s->rcr.pi) {
			s->rcr.valid_bit ^= BM_VALID_BIT;
		}
	}

	if (!(flags & SWP_F_RCR_VB)) {
		n = bm_cyc_diff(s->rcr.pi_ci_mask, s->rcr.fi, s->rcr.pi);
		if (n && ((n >= s->rcr.pi_ci_mask >> 2) || flush)) {
			dma_wmb();
			bman_cena_write(s, BCSP_CENA_RCR_PI, s->rcr.pi);
			dccvac(bman_cena_base(s, BCSP_CENA_RCR_PI));
			s->rcr.fi = s->rcr.pi;
		}
	}

	return 0;
}
